/** @file oal_dnld.c
  *
  * @brief This file contains the ar8030 firmware download function defines.
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
  */
/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>

#include "utils/utils.h"
#include "oal_main.h"
#include "oal_dnld.h"

/****************************
 * Local params
 ****************************/

/** Firmware name */
static char *fw_name = NULL;
/** Config name */
static char *cfg_name = NULL;

/** Timeout of fw dnld req */
#define OAL_DNLD_REQ_TIMEOUT         1000

/****************************
 * Function defines
 ****************************/
/**
 *  @brief Download the firmware to ar8030
 *
 *  @param handle   A pointer to oal_handle structure
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_init_fw(oal_handle *handle)
{
    oal_status status = OAL_STATUS_SUCCESS;
    int ret = 0;
    int offset = 0;
    const uint8_t *data;
    int timeout = OAL_DNLD_REQ_TIMEOUT;
    STRU_SPL_HEADER* spl_header = NULL;

    AR_DBG(DM_DNLD, DL_MSG, "fw_name %s cfg_name %s\n", fw_name, cfg_name);

    if (!handle) {
        AR_DBG(DM_DNLD, DL_ERR, "handle is NULL\n");
        goto end;
    }

    if (!handle->callbacks.oal_send_dnld_req) {
        AR_DBG(DM_DNLD, DL_ERR, "No send dnld req function.\n");
        goto end;
    }

    if (fw_name) {
        /** Open the firmware file */
        ret = request_firmware(&handle->firmware, fw_name, handle->dev);
        if (ret) {
            AR_DBG(DM_DNLD, DL_ERR, "request_firmware %s failed! ret %d\n", fw_name, ret);
            goto end;
        }
    }
    else {
        ret = -2;
        goto end;
    }

    if (cfg_name) {
        /** Open the cfg file */
        ret = request_firmware(&handle->cfg, cfg_name, handle->dev);
        if (ret) {
            AR_DBG(DM_DNLD, DL_ERR, "request cfg %s failed! ret %d\n", cfg_name, ret);
        }
    }

    data = handle->firmware->data;
    /** parsing upgrade file header,to get load address for each partion */
    spl_header = (STRU_SPL_HEADER*)data;

    /** spl header */
    if (handle->callbacks.oal_send_dnld_req(handle, SPL_HEADER_LOAD_ADDR, (u8*)data, spl_header->header_len, timeout) < 0)
    {
        AR_DBG(DM_DNLD, DL_ERR, "data transfer_data failed!\n");
        ret = -3;
        goto end;
    }

    offset += ROUNDUP(spl_header->header_len, SPL_IMAGE_SIZE_ALIGN);

    if (spl_header->troot_len)
    {
        if ((ret = handle->callbacks.oal_send_dnld_req(handle, spl_header->troot_load_addr,
            (u8*)(data + offset), spl_header->troot_len, timeout)) < 0)
        {
            AR_DBG(DM_DNLD, DL_ERR, "upgrade transfer failed!(addr: 0x%x)\n", spl_header->troot_load_addr);
        }

        offset += ROUNDUP(spl_header->troot_len, SPL_IMAGE_SIZE_ALIGN);

        if ((ret = handle->callbacks.oal_send_dnld_req(handle, spl_header->signature_load_addr,
            (u8*)(data + offset), spl_header->signature_len, timeout)) < 0)
        {
            AR_DBG(DM_DNLD, DL_ERR, "upgrade transfer failed!(addr: 0x%x)\n", spl_header->signature_load_addr);
        }

        offset += ROUNDUP(spl_header->signature_len, SPL_IMAGE_SIZE_ALIGN);
    }

    //send spl
    if ((ret = handle->callbacks.oal_send_dnld_req(handle, spl_header->spl_load_addr,
        (u8*)(data + offset), spl_header->spl_len, timeout)) < 0)
    {
        AR_DBG(DM_DNLD, DL_ERR, "upgrade transfer failed!(addr: 0x%x)\n", spl_header->spl_load_addr);
    }

    AR_DBG(DM_DNLD, DL_MSG, "upgrade spl success!\n");

    if (handle->cfg) {
        data = handle->cfg->data;
        if ((ret = handle->callbacks.oal_send_dnld_req(handle, spl_header->signature_load_addr,
            (u8*)data, handle->cfg->size, timeout)) < 0)
        {
            AR_DBG(DM_DNLD, DL_ERR, "upgrade bb_cfg failed!(addr: 0x%x)\n", spl_header->signature_load_addr);
        }
        AR_DBG(DM_DNLD, DL_MSG, "upgrade bb_cfg success!\n");
    }

    //end usb boot, ar9301 will goto bl2
    if (handle->callbacks.oal_send_dnld_done) {
        if ((ret = handle->callbacks.oal_send_dnld_done(handle, timeout)) < 0)
        {
            AR_DBG(DM_DNLD, DL_ERR, "usb_exit_romcode failed!ret %d\n", ret);
            goto end;
        }
    }

    AR_DBG(DM_DNLD, DL_MSG, "exit_usb_boot_in_bl1 bb_cfg success!\n");

end:
    if (handle->firmware)
        release_firmware(handle->firmware);
    if (handle->cfg)
        release_firmware(handle->cfg);

    if (ret) {
        status = OAL_STATUS_FAILURE;
    }

    return status;
}

module_param(fw_name, charp, 0);
MODULE_PARM_DESC(fw_name, "FW Name");

module_param(cfg_name, charp, 0);
MODULE_PARM_DESC(cfg_name, "Config Name");
